Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring for adding other sources #4

Merged
merged 21 commits into from
Mar 14, 2024

Conversation

savnadya
Copy link
Collaborator

No description provided.

@savnadya savnadya requested a review from l0kix2 February 28, 2024 14:28
config.go Outdated
@@ -73,6 +76,8 @@ type YtsaurusConfig struct {
DebugUsernames []string `yaml:"debug_usernames"`
// DebugGroupnames is a list of YTsaurus groupnames for which app will print more debug info in logs.
DebugGroupnames []string `yaml:"debug_groupnames"`

SourceAttributeName *string `yaml:"source_attribute_name"`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As with other fields it is ok to have one empty value: empty string. You making own life harder making it pointer.

Copy link
Collaborator

@l0kix2 l0kix2 Mar 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make it simple string as we discussed + add != "" check in app start (ytsaurus object creation to be specific).

@@ -40,31 +45,37 @@ func doGetAllYtsaurusUsers(ctx context.Context, client yt.Client) ([]YtsaurusUse
var users []YtsaurusUser
for _, ytUser := range response {
var bannedSince time.Time
if ytUser.BannedSince != "" {
bannedSince, err = time.Parse(appTimeFormat, ytUser.BannedSince)
if bannedSinceRaw, ok := ytUser.Attrs[bannedSinceAttributeName]; ok && bannedSinceRaw != "" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't Attrs be nil? I've had such panics when I've run tests here https://github.com/nebius/ytsaurus-active-directory-integration/pull/3/files before I've add a check for nil.

source_models.go Outdated
@@ -0,0 +1,119 @@
package main
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code in this file should be moved to the other files (packages in the future) to be flexible and aligned with golang philosophy.
In short, golang interfaces are not intended to be used like interfaces in OOP languages, they are more like duck-typing in dynamic languages. There couple rules of thumb here:

  • interfaces are declared in the place of usage (client code), often such interfaces are private for client package;
  • functions should accept interfaces and return structs (it is very good rule for New functions espessially).

This is an article i've googled which can give the idea: https://alok87.in/blog/interfaces-go

Therefore:

  1. All interfaces should be defined in the place of usage:
  • Source should move in app.go
  • SourceUser, SourceGroup in diff.go
  • SourceGroupWithMembers also in diff.go
  1. AzureUser, AzureGroup and everything Azure should be in azure*.go
  2. NewSource{User,Group} functions shouldn't exists. It should be NewAzureUser (and NewLdapUser in the future) in azure*.go which should return AzureUser struct (not SourceUser interface).

app_test.go Outdated
@@ -3,6 +3,7 @@ package main
import (
"context"
"fmt"
"go.ytsaurus.tech/library/go/ptr"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File is not goimports-ed with -local go.ytsaurus.tech (goimports)

actually I think we should remove that rule from linter config, because this library is external to this project.

if sourceTypeRaw, ok := ytUser.Attrs[sourceTypeAttributeName]; ok {
sourceType = SourceType(sourceTypeRaw.(string))
}
sourceUser, err = NewSourceUser(sourceType, sourceRaw.(map[string]any))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I've wrote this code initially I didn't succeed to make the attribute configurable and ytsaurus code knows too much about azure fields, but it is not its area of responsibility (only knowing how to communicate with ytsaurus is). Now since we can configure attribute we can fix that flaw.

I suggest we store map[string]any in YtsaurusUser.SourceData and let app code delegate parsing it to the source (azure/ldap) if needed (I'm actually not sure if we need to parse map to AzureUser/LdapUser at all, because as far as i remember we only serialize one way AzureUser > YtsaurusUser).
I.e. app.buildYtsaurusUser should either call Source method or maybe simpler AzureUser/LdapUser/SourceUser should have a method like AsMap() map[string]any and its result being written in YtsaurusUser.SourceData field.

That way we ensure our components are loosely coupled.

const (
bannedSinceAttributeName = "banned_since"
bannedAttributeName = "banned"
sourceTypeAttributeName = "source_type"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to store that in ytsaurus, we have it in config.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we don't. In config we have only SourceAttributeName (for "azure" or "source" values).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my bad, but still think since source is configured in the app we can delegate handling source-fields to that source without storing it in attributes.

FirstName: aliceAzure.FirstName,
LastName: aliceAzure.LastName,
DisplayName: aliceAzure.DisplayName,
},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets simplify diff for tests by introducing some helper structs like testYtsaurusAzureUser with same fields as current YtsaurusUser and call converter function where needed in test code.

Copy link
Collaborator

@l0kix2 l0kix2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't checked tests changes and diff algorithm changes yet, only main code structure.

SourceGroup SourceGroup
// Members is a set of strings, representing users' ObjectID.
Members StringSet
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These interfaces/structs are belong to app files, not to ytsaurus files. Particullary to diff.go since they used only there.

diff.go Outdated
if ytUser.IsManuallyManaged(a.source.GetSourceType()) {
return nil, errors.New("user is manually managed and can't be converted to source user")
}
sourceType := SourceType(*ytUser.SourceType)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I said earlier I don't see the need to duplicate source type (which is already configured in config file) in each user in ytsaurus.
I suggest we simple add NewUser method in Source interface, implement it in azure source (calling NewAzureUser) and here we may simply call a.source.NewUser.

That way code would be cleaner because:

  1. we don't need to duplicate and store source type in all users (and we don't need to migrate our data).
  2. we don't need to check source type in the runtime — source method existence would be checked in the compile time
  3. we don't need to support list of possible source types here.

}
}
if sourceRaw, ok := ytUser.Attrs[sourceAttributeName]; ok {
// For backward compatibility only.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can break backward compatibility here since I am the only app user currently.
Just lets check that sourceAttribute is not empty at the app start.

config.go Outdated
@@ -73,6 +76,8 @@ type YtsaurusConfig struct {
DebugUsernames []string `yaml:"debug_usernames"`
// DebugGroupnames is a list of YTsaurus groupnames for which app will print more debug info in logs.
DebugGroupnames []string `yaml:"debug_groupnames"`

SourceAttributeName *string `yaml:"source_attribute_name"`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a comment that it will be the attribute name of user object in ytsaurus.

config.go Outdated
@@ -73,6 +76,8 @@ type YtsaurusConfig struct {
DebugUsernames []string `yaml:"debug_usernames"`
// DebugGroupnames is a list of YTsaurus groupnames for which app will print more debug info in logs.
DebugGroupnames []string `yaml:"debug_groupnames"`

SourceAttributeName *string `yaml:"source_attribute_name"`
Copy link
Collaborator

@l0kix2 l0kix2 Mar 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make it simple string as we discussed + add != "" check in app start (ytsaurus object creation to be specific).

func (u YtsaurusUser) IsManuallyManaged() bool {
return u.AzureID == ""
func (u YtsaurusUser) IsManuallyManaged(sourceType SourceType) bool {
return u.SourceRaw == nil || u.SourceType == nil || *u.SourceType != string(sourceType)
}
Copy link
Collaborator

@l0kix2 l0kix2 Mar 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically managed user must have non empty/non nil SourceRaw, let's just check emptiness of this map. And so we don't accidentally initialise this structure wrongly lets add:

  • NewAutoManagedYtsaurusUser(username, sourceRaw, bannedSince) function, that would return error if function is called with empty sourceRaw.
  • NewManualManagedYtsaurusUser(username) for creating manually managed users.

diff.go Outdated
for _, user := range ytUsers {
if user.IsManuallyManaged(a.source.GetSourceType()) {
continue
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks paranoid since GetUsers shouldn't and don't return such users. We must (and maybe already have) have test case for such cases instead of runtime check

diff.go Outdated
}
sourceUser, err := a.buildSourceUser(&user)
if err != nil {
return nil, errors.Wrap(err, "failed to create azure user from source")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

failed to build source user

if err != nil {
return nil, err
}
return &azureUser, nil
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This marshall/unmarshall trick is cool, but it may give misleading feeling that AzureUser struct somehow related to yson encoding/decoding. But I think json and any other encoder can be here.

I suppose this small lib https://pkg.go.dev/github.com/mitchellh/mapstructure#example-Decode can help to replace yson marshal/unmarshal boilerplate here and in GetRaw() and same code in Groups and in future ldap source code.

But I not insist on changing this code.

@@ -14,7 +14,7 @@ import (
//go:embed config.local.yaml
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

syntax in this file and in app_integration_test.go is broken after signature changes.

If you use idea/goland you can add integration in custom tags field in go > build tags setting to see such issues more easily.

@@ -14,7 +14,7 @@ import (
//go:embed config.local.yaml
var _localConfig embed.FS

// TestPrintAzureUsersIntegration tests nothing, but can be used to debug Azure users retrieved from ms graph api.
// TestPrintAzureUsersIntegration tests nothing, but can be used to debug Source users retrieved from ms graph api.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert back to azure i suppose

source_models.go Outdated

const (
AzureSourceType SourceType = "azure"
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose after the changes I suggest earlier we won't need this constant.

util.go Outdated
appTimeFormat = "2006-01-02T15:04:05Z0700"
defaultAzureTimeout = 3 * time.Second
defaultAzureSecretEnvVar = "AZURE_CLIENT_SECRET"
defaultAppRemoveLimit = 10
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

THis should be in azure_real.go

ytsaurus.go Outdated
if err != nil {
return false, err
}
return attrAzureExists || attrSourceExists, nil
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets use sourceAttributeName here.

Copy link
Collaborator

@l0kix2 l0kix2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like only minor things left, but let's fix diffUsers a bit (i haven't check diffGroups yet, if it has same issues then let's improve both).

config.go Outdated
// UsersFilter is MS Graph $filter value used for user fetching requests.
// See https://learn.microsoft.com/en-us/graph/api/user-list?#optional-query-parameters
UsersFilter string `yaml:"users_filter"`
// GroupsFilter is MS Graph $filter value used for group fetching requests.
// See https://learn.microsoft.com/en-us/graph/api/group-list
GroupsFilter string `yaml:"groups_filter"`
GroupsFilter string `yaml:"groups_filter"`
Timeout time.Duration `yaml:"timeout"`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor, but don't understand why timeout field is moved between filters.

ytsaurus.go Outdated
@@ -60,6 +63,9 @@ func NewYtsaurus(cfg *YtsaurusConfig, logger appLoggerType, clock clock.PassiveC
if cfg.Timeout == 0 {
cfg.Timeout = defaultYtsaurusTimeout
}
if cfg.SourceAttributeName == "" {
cfg.SourceAttributeName = "source"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better make a const.

source_models.go Outdated
@@ -0,0 +1 @@
package main
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems it is an empty file now and can be deleted :)

create: create,
update: update,
remove: remove,
}
result: resultUsersMap,
}, nil
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since tests are okey, I assume diffUsers works correctly, but it seem overcomplicated a bit.
I see the implementation that way:
0. create maps sourceUsers [id]sourceUser and ytUsers [id]ytsaurusUser from slices

  1. iterate through sourceUsers map, check ytUsers map and append to create list
  2. iterate through ytUsers map, check sourceUsers map:
    • not found in sourceUsers go to delete list
    • changed go to update list.

Here we have
1)existedUsersMap which has two fields and one of them (sourceUser) is not used. So i suggest we rename it to ytUsers [id]ytsaurusUser.
2) ytUsersFromSourceMap which has the same keys as sourceUsers, so we can simple drop building ytUsersFromSourceMap and use sourceUsers for checking and call buildYtsaurusUser only for once we plan to create.

Also resultUsersMap is not being updating in case of user delete, though it should i guess (don't we have a test case for that?)

Copy link
Collaborator

@l0kix2 l0kix2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just couple of comments folllowing up yesterday review.

Let's fix those small issues I mention and merge.

diff.go Outdated
if err != nil {
return nil, errors.Wrap(err, "failed to create azure group from source")
}
existedGroupsWithMembersMap[sourceGroup.GetID()] = existedGroup{group, sourceGroup}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so existedGroup.sourceGroup is not used therefore code can be little simplified here as in diffUsers i suppose

@@ -36,7 +36,7 @@ func run(configFilePath string) error {
return errors.Wrapf(err, "failed to load config %s", configFilePath)
}

logger, err := configureLogger(cfg.Logging)
logger, err := configureLogger(&cfg.Logging)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this change must be supported in azure_real_integration_test.go also

diff.go Outdated
return nil, errors.Wrap(err, "failed to create Ytsaurus user from source user")
}
ytUsersFromSourceMap[user.GetID()] = ytUser
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think ytUsersFromSourceMap can be safely removed now, I don't see usages anymore

@l0kix2 l0kix2 merged commit bfab770 into tractoai:main Mar 14, 2024
1 check passed
l0kix2 added a commit that referenced this pull request Mar 25, 2024
l0kix2 added a commit that referenced this pull request Mar 25, 2024
* sha for docker tag

* sha for docker tag #2

* sha for docker tag #3

* sha for docker tag #4
l0kix2 added a commit that referenced this pull request Jun 11, 2024
l0kix2 added a commit that referenced this pull request Jun 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants